#!/bin/bash

source /home/shell/util.sh

printInfo "`openssl version`";
printInfo "配置文件："`openssl version -a|grep OPENSSLDIR|perl -pe "s/.*\"(.*)\".*/\1/g"`/openssl.cnf
printInfo "文档：https://www.openssl.org/docs/man1.1.1/";

function userInput () {
  eval tmp1=$(echo '$'"$1");
  read -p $2"["$tmp1"]：" tmp2
  tmp2=${tmp2:-$tmp1}
  writeProperties $1 $tmp2
}


function makeCaCert () {

  if [ ! -d "${cer_path}" ]; then
      init
      changeDir
  fi


  userInput validay "请输入证书全局有效天数"
  userInput ca_passwd "请输入CA密码"
  userInput ca_home_url "CA站点网址"
  userInput ca_company_name "CA公司名称"
  userInput ca_department_name "CA部门名称"

  # CA私钥（带密码）
  openssl genrsa -des3 -passout pass:${ca_passwd} -out ${cer_path}/ca_pkcs1_private_key.pem 2048
  # CA私钥（去密码）
  openssl rsa -in ${cer_path}/ca_pkcs1_private_key.pem -out ${cer_path}/ca_pkcs1_private_key_nopasswd.pem -passin pass:${ca_passwd}

  # 生成CA证书
  openssl req -new -x509 -utf8 -passin pass:${ca_passwd} -days ${validay} \
  -subj "/CN=${ca_home_url}/C=CN/ST=CHONGQING/L=CQ/O=${ca_company_name}/OU=${ca_department_name}" \
  -key ${cer_path}/ca_pkcs1_private_key.pem -out ${cer_path}/ca_rsa_public_key.crt

  # 导出CRL文件
  openssl ca -gencrl -cert ${cer_path}/ca_rsa_public_key.crt -keyfile ${cer_path}/ca_pkcs1_private_key_nopasswd.pem -crldays ${validay} -out ${cer_path}/ca.crl

  printInfo "证书指纹："
  openssl x509 -fingerprint -noout -in ${cer_path}/ca_rsa_public_key.crt
}

function makeCaLevel2 () {

  userInput validay "请输入证书全局有效天数"
  userInput ca2_passwd "请输入子CA证书密码"
  userInput ca2_home_url "子CA站点网址"
  userInput ca2_company_name "子CA证书公司名称"
  userInput ca2_department_name "子CA证书部门名称"

  if [ ! -f "${cer_path}/ca_pkcs1_private_key_nopasswd.pem" ]; then
      printFail "还未生成根证书，请重新运行程序！"
      exit;
  fi

  # 生成CA2私钥
  openssl genrsa -des3 -passout pass:${ca2_passwd} -out ${cer_path}/ca2_pkcs1_private_key.pem 2048
  # 去CA2私钥密码
  openssl rsa -passin pass:${ca2_passwd} -in ${cer_path}/ca2_pkcs1_private_key.pem -out ${cer_path}/ca2_pkcs1_private_key_nopasswd.pem
  # CA2申请
  openssl req -new -passin pass:${ca2_passwd} -utf8 \
  -subj "/C=CN/ST=CHONGQING/L=CQ/O=${ca2_company_name}/OU=${ca2_department_name}/CN=${ca2_home_url}" \
  -key ${cer_path}/ca2_pkcs1_private_key.pem -sha512 -out ${cer_path}/ca2_ca_req.csr
  # CA2证书
  openssl ca -extensions v3_ca -in ${cer_path}/ca2_ca_req.csr -passin pass:${ca_passwd} -days ${validay} \
  -cert ${cer_path}/ca_rsa_public_key.crt -keyfile ${cer_path}/ca_pkcs1_private_key.pem -batch \
  -out ${cer_path}/ca2_rsa_public_key.crt
  # 清理
  rm -rf ${cer_path}/ca2_ca_req.csr
  printInfo "证书指纹："
  openssl x509 -fingerprint -noout -in ${cer_path}/ca2_rsa_public_key.crt
}

function makeServerCert () {

  userInput validay "请输入证书全局有效天数"
  userInput server_passwd "请输入服务器证书密码"
  userInput server_home_url "服务器站点网址"
  userInput server_company_name "服务器证书公司名称"
  userInput server_department_name "服务器证书部门名称"

  if [ ! -f "${cer_path}/ca_pkcs1_private_key_nopasswd.pem" ]; then
      printFail "还未生成根证书，请重新运行程序！"
      exit;
  fi
  

  # 生成服务端私钥
  openssl genrsa -des3 -passout pass:${server_passwd} -out ${cer_path}/server_pkcs1_private_key.pem 2048
  # 去服务端私钥密码
  openssl rsa -passin pass:${server_passwd} -in ${cer_path}/server_pkcs1_private_key.pem -out ${cer_path}/server_pkcs1_private_key_nopasswd.pem
  # 服务端申请
  openssl req -new -passin pass:${server_passwd} -utf8 \
  -subj "/C=CN/ST=CHONGQING/L=CQ/O=${server_company_name}/OU=${server_department_name}/CN=${server_home_url}" \
  -key ${cer_path}/server_pkcs1_private_key.pem -sha512 -out ${cer_path}/server_ca_req.csr
  # 服务端证书
  openssl x509 -req -in ${cer_path}/server_ca_req.csr -CA ${cer_path}/ca_rsa_public_key.crt -CAkey ${cer_path}/ca_pkcs1_private_key.pem -addtrust serverAuth \
  -passin pass:${ca_passwd} -CAcreateserial -out ${cer_path}/server_rsa_public.crt -days ${validay}
  # 清理
  rm -rf ${cer_path}/server_ca_req.csr
  printInfo "证书指纹："
  openssl x509 -fingerprint -noout -in ${cer_path}/server_rsa_public.crt
}

function makeClientCert () {
  userInput validay "请输入证书全局有效天数"
  userInput client_passwd "请输入客户端证书密码"
  userInput client_home_url "客户端名称"
  userInput client_company_name "客户端证书公司名称"
  userInput client_department_name "客户端证书部门名称"

  # 生成客户私钥
  openssl genrsa -des3 -passout pass:${client_passwd} -out ${cer_path}/client_pkcs1_private_key.pem 2048
  # 客户端申请
  openssl req -new -passin pass:${client_passwd} -utf8 -subj "/C=CN/ST=CHONGQING/L=CQ/O=${client_company_name}/OU=${client_department_name}/CN=${client_home_url}" \
  -key ${cer_path}/client_pkcs1_private_key.pem -sha512 -out ${cer_path}/client_ca_req.csr
  # 客户端证书
  openssl x509 -req -in ${cer_path}/client_ca_req.csr -CA ${cer_path}/ca_rsa_public_key.crt -CAkey ${cer_path}/ca_pkcs1_private_key.pem \
  -passin pass:${ca_passwd} -CAcreateserial -out ${cer_path}/client_rsa_public.crt -days ${validay} -addtrust clientAuth
  # 清理
  rm -rf ${cer_path}/client_ca_req.csr

  printInfo "证书指纹："
  openssl x509 -fingerprint -noout -in ${cer_path}/client_rsa_public.crt

  # 转换为浏览器证书
  openssl pkcs12 -export -name "power by jlcon" -clcerts -in ${cer_path}/client_rsa_public.crt -inkey ${cer_path}/client_pkcs1_private_key.pem -passin pass:${client_passwd} \
  -passout pass:${client_passwd} -out ${cer_path}/浏览器证书.p12

}

function invokeCert () {

  userInput validay "请输入证书全局有效天数"

  printInfo "证书列表："
  echo "===================================="
  ls ${cer_path}|grep crt|grep $1
  ls ${cer_path}|grep p12
  echo "===================================="
  read -p "请选择吊销证书：" invoke_cert

  if [[ "${invoke_cert##*.}" == "p12" ]]; then
    # 从pkcs8文件中提取证书
    openssl pkcs12 -clcerts -passin pass:${client_passwd} -nokeys -in  ${cer_path}/浏览器证书.p12 -out  ${cer_path}/from_pkcs12.crt
    invoke_cert_name=from_pkcs12.crt
  elif [[ "${invoke_cert##*.}" == "crt" ]]; then
    invoke_cert_name=${invoke_cert}
  else
    printFail "不支持${invoke_cert##*.}格式证书吊销"
    exit;
  fi
  
  # 备份crl和清理client证书
  cp -r ${cer_path}/ca.crl ${cer_path}/backup/ca_$(date +%Y%m%d).crl
  mv ${cer_path}/浏览器证书.p12 ${cer_path}/backup/client_revoked_$(date +%Y%m%d).p12
  mv ${cer_path}/client_rsa_public.crt ${cer_path}/backup/client_rsa_public_revoked_$(date +%Y%m%d).crt
  mv ${cer_path}/client_pkcs1_private_key.pem ${cer_path}/backup/client_pkcs1_private_key_revoked_$(date +%Y%m%d).pem


  # 吊销证书
  # -crl_reason unspecified、keyCompromise、CACompromise、CACompromise、affiliationChanged、superseded、cessationOfOperation、certificateHold、removeFromCRL
  openssl ca -revoke ${cer_path}/${invoke_cert_name} \
  -cert ${cer_path}/ca_rsa_public_key.crt -keyfile ${cer_path}/ca_pkcs1_private_key_nopasswd.pem -crl_reason superseded
  # 生成吊销列表
  openssl ca -gencrl -cert ${cer_path}/ca_rsa_public_key.crt -keyfile ${cer_path}/ca_pkcs1_private_key_nopasswd.pem -crldays ${validay} -out ${cer_path}/ca.crl

  printInfo "证书`openssl x509 -fingerprint -noout -in ${cer_path}/${invoke_cert_name}`已吊销："
  
  # 验证吊销文件是否正确
  printInfo "吊销文件校验："
  openssl crl -in ${cer_path}/ca.crl -CAfile ${cer_path}/ca_rsa_public_key.crt -noout
  # 查询证书是否被吊销
  printInfo "吊销结果验证(verification failed为成功验证)："
  openssl verify -crl_check -CRLfile ${cer_path}/ca.crl -CAfile ${cer_path}/ca_rsa_public_key.crt ${cer_path}/from_pkcs12.crt
  printInfo "CRL信息："
  openssl crl -in ${cer_path}/ca.crl -noout -text -hash
  rm -rf ${cer_path}/${invoke_cert_name}
}

function writeProperties () {
  home_dir="`echo ~`"
  if [[ -z "`grep $1 $home_dir/.nginx_ssl`" ]]; then
      echo "$1=$2" >> ~/.nginx_ssl
  else
      sed -i "s#^$1=.*#$1=$2#g" ~/.nginx_ssl
  fi
  source ~/.nginx_ssl
  # eval writeResult=$(echo '$'"$1");
  # echo "修改确认：${writeResult}"
}


function changeDir () {

  userInput cer_path "证书存放目录"

  if [ ! -d "${cer_path}" ]; then
      mkdir -p ${cer_path}
  fi

  if [ ! -d "${cer_path}/backup" ]; then
      mkdir -p ${cer_path}/backup
  fi

  if [ ! -f "${cer_path}/index.txt" ]; then
      touch ${cer_path}/index.txt
  fi

  if [ ! -f "${cer_path}/crlnumber" ]; then
      echo "00" > ${cer_path}/crlnumber
  fi

  if [ ! -f "${cer_path}/serial" ]; then
      echo "00" > ${cer_path}/serial
  fi

  sed -i "s@dir\s*=\s*\S*@dir             = "${cer_path}"@g" /usr/lib/ssl/openssl.cnf
  sed -i "s@stateOrProvinceName\s*=\s*match@stateOrProvinceName     = optional@g" /usr/lib/ssl/openssl.cnf

  cd ${cer_path}
}

function nginxPrint () {
cat<<EOF
===========================================nginx config===========================================
server {
    listen       443 ssl;
    server_name  ${host_addr};
    charset      utf-8;

    ssl_crl                 ${cer_path}/ca.crl;
    ssl_certificate         ${cer_path}/server_rsa_public.crt;
    ssl_certificate_key     ${cer_path}/server_pkcs1_private_key_nopasswd.pem;
    ssl_client_certificate  ${cer_path}/ca_rsa_public_key.crt;
    ssl_verify_client       on;

    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;

    ssl_ciphers  HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers  on;
}
===========================================nginx config===========================================
EOF
}

function showlist () {
    printInfo "CA证书===============================\n"
    printInfo "`ls ${cer_path}|grep ca|grep -v crl`"
    printInfo "\n服务器证书============================\n"
    printInfo "`ls ${cer_path}|grep server`"
    printInfo "\n客户端证书============================\n"
    printInfo "`ls ${cer_path}|grep client`"
    printInfo "`ls ${cer_path}|grep p12`"
    printInfo "\n证书吊销列表===========================\n"
    printInfo "`ls ${cer_path}|grep ca.crl`"
    printInfo "\n======================================"
}



function init () {
user_home=`echo ~`;
if [ ! -f "${user_home}/.nginx_ssl" ]; then
    printWarn "配置文件不存在，创建！"
tee ~/.nginx_ssl<<EOF
validay=364
cer_path=/home/certs

ca_passwd=123456
ca_home_url=www.demorootca.com
ca_company_name=mycompany
ca_department_name=mydepartment

ca2_passwd=123456
ca2_home_url=www.democa2.com
ca2_company_name=mycompany
ca2_department_name=mydepartment

server_passwd=123456
server_home_url=www.server.com
server_company_name=servercompany
server_department_name=serverdepartment

client_passwd=123456
client_home_url=myclient
client_company_name=clientcompany
client_department_name=clientdepartment
EOF
fi
source ~/.nginx_ssl
}

# ===========================================================================================================================================================
init
while [ true ]; do
cat<<EOF
    1.  更换工作目录($cer_path)
    2.  生成CA证书
    3.  生成CA证书（二级）
    4.  生成服务器证书（ROOT CA）
    5.  生成客户端证书（ROOT CA）
    6.  吊销客户端端证书
    7.  吊销服务器证书
    8.  导出服务端证书
    9.  导出客户端证书
    10. 清除服务器证书
    11. 清除客户端证书
    12. 复位（慎用）
    13. Nginx双向认证配置
    14. 查看证书列表
    15. 导出Nginx双向认证证书
EOF
  read -p "请选择[Q退出]：" install_option
  case "${install_option}" in
    1)
      changeDir
    ;;
    2)
      makeCaCert
    ;;
    3)
      makeCaLevel2
    ;;
    4)
      makeServerCert
    ;;
    5)
      makeClientCert
    ;;
    6)
      invokeCert client
    ;;
    8)
      cd ${cer_path}
      tar -zcP -f server_cert_all.tar.gz server_*
      sz server_cert_all.tar.gz
      rm -rf server_cert_all.tar.gz
    ;;
    9)
      cd ${cer_path}
      tar -zcP -f client_cert_all.tar.gz client_* *.p12
      sz client_cert_all.tar.gz
      rm -rf client_cert_all.tar.gz
    ;;
    15)

      cd ${cer_path}
      tar -zcP -f nginx_cert_all.tar.gz *.p12 ca.crl server_rsa_public.crt server_pkcs1_private_key_nopasswd.pem ca_rsa_public_key.crt
      sz nginx_cert_all.tar.gz
      rm -rf nginx_cert_all.tar.gz
    ;;
    10)
      rm -rf ${cer_path}/server*
    ;;
    11)
      rm -rf ${cer_path}/client*
      rm -rf ${cer_path}/*.p12
    ;;
    12)
      rm -rf ~/.nginx_ssl
      rm -rf ${cer_path}
    ;;
    13)
      nginxPrint
    ;;
    14)
      showlist
    ;;
    q|Q)
      exit;
    ;;
    *)
      printFail "选择错误，重新选择。"
    ;;
  esac
done